#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kprobes.h>
#include <linux/uaccess.h>
#include <linux/i2c.h>
#include <linux/sched.h>
#include <asm/ptrace.h>
#include <linux/workqueue.h>
#include <drm/drmP.h>
#include <drm/drm_connector.h>
#include <drm/drm_drv.h>
#include <drm/drm_crtc_helper.h>


#include <../drivers/gpu/drm/loongson/loongson_drv.h>

//#include "asm/mach-loongson/loongson-pch.h"

struct ls_i2c_dev {
  spinlock_t    lock;
  unsigned int    suspended:1;
  struct device   *dev;
  void __iomem    *base;
  int     irq;
  struct completion cmd_complete;
  struct resource   *ioarea;
  struct i2c_adapter  adapter;
};


rwlock_t *pls7a_rwlock;
unsigned long *pls7a_rwflags;

#define ls7a_rwlock *pls7a_rwlock
#define ls7a_rwflags *pls7a_rwflags

#define DLEF "drm_load_edid_firmware"
#define OPE "output_poll_execute"
#define DMGC "drm_mode_getconnector"
#define DHPSCM "drm_helper_probe_single_connector_modes"
#define LCD "loongson_connector_detect"
#define LOONGSON_GET_MODES "loongson_vga_get_modes"
#define LCMS "loongson_crtc_mode_set"
#define DAEM "drm_add_edid_modes"

#define LS_I2C_PRER_LO_REG  0x0
#define LS_I2C_PRER_HI_REG  0x1
#define LS_I2C_CTR_REG      0x2
#define LS_I2C_TXR_REG      0x3
#define LS_I2C_RXR_REG      0x3
#define LS_I2C_CR_REG       0x4
#define LS_I2C_SR_REG       0x4

#define ls_i2c_writeb(val, addr)  writeb(val, ls_i2c_dev->base + addr)

unsigned long dc_base = 0;

struct loongson_connector *gls_connector = NULL;
struct drm_device *pgdrm_device = NULL;
struct drm_mode_get_connector *pgout_resp = NULL;
struct i2c_adapter *ddc_adap = NULL;
struct ls_i2c_dev *ls_i2c_dev = NULL;

static int entry_handler_dmgc(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  struct drm_device *pdrm_device = NULL;
  struct drm_connector *pdrm_connector = NULL;
  struct loongson_connector *ls_connector = NULL;
  struct drm_encoder *pdrm_encoder = NULL;
  struct loongson_encoder *ls_encoder = NULL;
  struct drm_mode_get_connector *pout_resp = NULL;

  pr_info("\n----------------------\n<%s>:\n", __func__);

#ifdef CONFIG_MIPS

  pdrm_device = (struct drm_device *)(regs->regs[4]);
  pgdrm_device = pdrm_device;
  pout_resp = (struct drm_mode_get_connector *)(regs->regs[5]);
  pgout_resp = pout_resp;

  pdrm_connector = drm_connector_lookup(pdrm_device, pout_resp->connector_id);
  ls_connector = to_loongson_connector(pdrm_connector);

  gls_connector = ls_connector;

  if (ls_connector != NULL && ls_connector->connector_id == 1) {
    //pdrm_encoder = drm_encoder_find(pdrm_device, pout_resp->encoder_id);
    pdrm_encoder = pdrm_connector->state ? pdrm_connector->state->best_encoder : pdrm_connector->encoder;
    if (pdrm_encoder == NULL) {
      pr_err("ls: pdrm_encoder get failed!\n");

      list_for_each_entry(pdrm_encoder, &pdrm_device->mode_config.encoder_list, head) {
        pr_info("pdrm_encoder->name:%s, index:%d, id:%d\n", pdrm_encoder->name, pdrm_encoder->index, pdrm_encoder->base.id);
        if (pdrm_encoder->index == 1) {
          break;
        }
      }
    }

    ls_encoder = to_loongson_encoder(pdrm_encoder);
    if (ls_encoder == NULL) {
      pr_err("ls: ls_encoder get failed!\n");
    }

    ls_connector->i2c->adapter = ddc_adap;
    //ls_connector->connector_vbios[ls_connector->connector_id]->i2c_id = 1;
    ls_encoder->i2c->adapter = ddc_adap;
    //ls_encoder->encoder_vbios[ls_encoder->encoder_id]->i2c_id = 1;
  }


  pr_info("pdrm_connector:%p, connector_id:%d, encoder_id:%d, count_modes:%d, pout_resp:%p\n", pdrm_connector, pout_resp->connector_id, pout_resp->encoder_id, pout_resp->count_modes, pout_resp);
  pr_info("driver_features:%d, DRIVER_MODESET:%d\n", pdrm_device->driver->driver_features, DRIVER_MODESET);
  pr_info("connector->override_edid:%d\n", pdrm_connector->override_edid);
  pr_info("ls_connector->connector_id:%d\n", ls_connector->connector_id);
  if (ls_connector->connector_id == 1)
    pr_info("ls_encoder->encoder_id:%d\n", ls_encoder->encoder_id);
  pr_info("pdrm_device->mode_config.poll_running:%d\n", pdrm_device->mode_config.poll_running);
  pr_info("pdrm_device->mode_config.poll_enabled:%d\n", pdrm_device->mode_config.poll_enabled);
  pr_info("drm_connector: force:%d, DRM_FORCE_ON:%d\n", pdrm_connector->force, DRM_FORCE_ON);


  pr_info("pdrm_device:%p\n", pdrm_device);
  pr_info("{%s} handler_pre: current_task.name:%s, p->addr = 0x%p, status = 0x%lx\n",
  ri->rp->kp.symbol_name, current->comm, ri->rp->kp.addr, regs->cp0_status);

#endif

	return 0;
}

static int ret_handler_dmgc(struct kretprobe_instance *ri, struct pt_regs *regs)
{
  unsigned long retval = (unsigned long)regs_return_value(regs);

  pr_info("%s return %ld", ri->rp->kp.symbol_name, retval);
  if (gls_connector != NULL)
    pr_info(", connector_id:%d\n", gls_connector->connector_id);
  pr_info("-----------------------------------------\n\n");

  pgout_resp = NULL;

  return 0;
}


static struct kretprobe kretp_dmgc = {
  .entry_handler = entry_handler_dmgc,
  .handler = ret_handler_dmgc,
  .data_size = 0,
  .maxactive = 20,
};


static int pc_module_init(void)
{
	int ret;
  unsigned long val = 0;
  struct drm_device *pdrm_device = NULL;

	pr_info("%s---%d\n", __func__, __LINE__);

  /* 1. initial init */
  pls7a_rwlock = (rwlock_t *)kallsyms_lookup_name("ls7a_rwlock");
  if (!pls7a_rwlock) {
    pr_err("lookup ls7a_rwlock failed!\n");
    return -1;
  }

  pls7a_rwflags = (unsigned long *)kallsyms_lookup_name("ls7a_rwflags");
  if (!pls7a_rwflags) {
    pr_err("lookup ls7a_rwflags failed!\n");
    return -1;
  }

  val = readl((void *)0x90000e0010010440);
  pr_info("ls: val:0x%lx", val);
  writel(val | (1 << 5), (void *)0x90000e0010010440);

  ddc_adap = i2c_get_adapter(1);
  if (ddc_adap == NULL) {
    pr_err("ls: i2c_get_adapter failed!\n");
    return -1;
  }
  pr_info("ls: ddc_adap->name:%s, ddc_adap->nr:%d\n", ddc_adap->name, ddc_adap->nr);

  ls_i2c_dev = (struct ls_i2c_dev *)i2c_get_adapdata(ddc_adap);
  ls_i2c_writeb(0xa0, LS_I2C_CTR_REG);

  /* 2, kprobe plant */
  /* 2.1, kretprobe plant */
  kretp_dmgc.kp.symbol_name = DMGC;
  ret = register_kretprobe(&kretp_dmgc);
  if (ret < 0) {
    pr_err("register_kretprobe failed, returned %d\n", ret);
    return ret;
  }
  pr_info("Planted return probe at %s: %p\n",
    kretp_dmgc.kp.symbol_name, kretp_dmgc.kp.addr);

	return 0;
}

static void pc_module_exit(void)
{
	pr_info("%s---%d\n", __func__, __LINE__);

  unregister_kretprobe(&kretp_dmgc);
  pr_info("kretprobe at %p unregistered\n", kretp_dmgc.kp.addr);

	return ;
}

module_init(pc_module_init);
module_exit(pc_module_exit);

MODULE_LICENSE("GPL");

